//
//  Route.swift
//  XBasicKit
//
//  Created by x on 2024/8/31.
//

import Foundation
//import Hero

// MARK: - 路由器类

public typealias URLPattern = String
public typealias ViewControllerFactory = (_ url: URLConvertible, _ values: [String: Any], _ context: Any?) -> UIViewController?
public typealias URLOpenHandlerFactory = (_ url: URLConvertible, _ values: [String: Any], _ context: Any?) -> Bool
public typealias URLOpenHandler = () -> Bool

open class Router<Route: AppRoute> {
    
    public weak var navigationController: UINavigationController?
    
    public let matcher = URLMatcher()
    
    private let interceptors: [RouteInterceptor]
    
    private var viewControllerFactories = [URLPattern: ViewControllerFactory]()
    private var handlerFactories = [URLPattern: URLOpenHandlerFactory]()
    
    public init(navigationController: UINavigationController?, interceptors: [RouteInterceptor] = []) {
        self.navigationController = navigationController
        self.interceptors = interceptors
    }
    
    @discardableResult
    open func navigate(to route: Route, animated: Bool = true) -> UIViewController? {
        return performNavigation(to: route, animated: animated)
    }
    
    
    ///  模式匹配，然后走的 navigate(to route: Route, animated: Bool = true)
    /// navigate(to url ）带to：
    @discardableResult
    open func navigate(to url: URLConvertible, wrap: UINavigationController.Type? = nil, from: UIViewController? = nil, animated: Bool = true) -> UIViewController? {
        guard let route = Route.from(url: url) else {
            interceptors.forEach { $0.didFailToNavigate(to: .url(url), error: RouteError.invalidURL(URL: url)) }
            return nil
        }
        return performNavigation(to: route, animated: animated, wrap: wrap, from: from)
    }
    
    /// 需要使用register 进行注册
    /// 从 URL 导航
    /// navigator.register("navigator://user/<username>") { url, values, context in
    /// guard let username = values["username"] as? String else { return nil }
    /// return UserViewController(navigator: navigator, username: username)
    /// }
    ///
    ///  不带to， 直接写 navigate("navigator://user/apple")
    @discardableResult
    open func navigate(_ url: URLConvertible, context: Any? = nil, wrap: UINavigationController? = nil, animated: Bool = true) -> UIViewController? {
        
        guard let viewController = self.viewController(for: url, context: context) else {
            interceptors.forEach { $0.didFailToNavigate(to: nil, error: RouteError.invalidURL(URL: url)) }
            return nil
        }
        
        guard (viewController is UINavigationController) == false else { return nil }
        guard let navigationController = wrap ?? UIViewController.topMost?.navigationController else { return nil }

        let interceptorChain = InterceptorChain(interceptors: interceptors)
        
        interceptorChain.proceed(route: .url(url)) { [weak self] shouldNavigate in
            guard let self = self else { return }
            if !shouldNavigate {
                if !shouldNavigate {
                    self.handleNavigationBlocked(.url(url))
                    return
                }
            }

            navigationController.pushViewController(viewController, animated: animated)
            
        }
        interceptors.forEach { $0.didNavigate(to: .url(url)) }
        return viewController
    }
    @discardableResult
    open func navigate(_ url: URLConvertible, context: Any? = nil, wrap: UINavigationController.Type? = nil, from: UIViewController?, animated: Bool = true, completion: (() -> Void)? = nil) -> UIViewController? {
        
        guard let viewController = self.viewController(for: url, context: context) else {
            interceptors.forEach { $0.didFailToNavigate(to: nil, error: RouteError.invalidURL(URL: url)) }
            return nil
        }
        let interceptorChain = InterceptorChain(interceptors: interceptors)
        
        guard let fromViewController = from ?? UIViewController.topMost else { return nil }

        interceptorChain.proceed(route: .url(url)) { [weak self]  shouldNavigate in
            guard let self = self else { return }
            if !shouldNavigate {
                if !shouldNavigate {
                    self.handleNavigationBlocked(.url(url))
                    return
                }
            }
            let viewControllerToPresent: UIViewController
            if let navigationControllerClass = wrap, (viewController is UINavigationController) == false {
                viewControllerToPresent = navigationControllerClass.init(rootViewController: viewController)
            } else {
                viewControllerToPresent = viewController
            }
            fromViewController.present(viewControllerToPresent, animated: animated, completion: completion)
        }
        interceptors.forEach { $0.didNavigate(to: .url(url)) }
        return viewController
    }
    
    @discardableResult
    open func navigate(_ url: URLConvertible, context: Any? = nil, wrap: UINavigationController.Type? = nil, from: UIViewController?) -> UIViewController? {
        let interceptorChain = InterceptorChain(interceptors: interceptors)
        
        guard let viewController = self.viewController(for: url, context: context) else {
            interceptors.forEach { $0.didFailToNavigate(to: nil, error: RouteError.invalidURL(URL: url)) }
            return nil
        }
        
        guard let fromViewController = from ?? UIViewController.topMost else { return nil }

        interceptorChain.proceed(route: .url(url)) { [weak self] shouldNavigate in
            guard let self = self else { return }
            if !shouldNavigate {
                if !shouldNavigate {
                    self.handleNavigationBlocked(.url(url))
                    return
                }
            }
            let viewControllerToPresent: UIViewController
            if let navigationControllerClass = wrap, (viewController is UINavigationController) == false {
              viewControllerToPresent = navigationControllerClass.init(rootViewController: viewController)
            } else {
              viewControllerToPresent = viewController
            }
            fromViewController.showDetailViewController(viewControllerToPresent, sender: nil)
            return
        }
        
        interceptors.forEach { $0.didNavigate(to: .url(url)) }
        return viewController
    }
}

public extension Router {
    func register(_ pattern: URLPattern, _ factory: @escaping ViewControllerFactory) {
        self.viewControllerFactories[pattern] = factory
    }
    
    func register(_ URLDicts: [URLPattern: ViewControllerFactory]) {
        for (key, value) in URLDicts {
            register(key, value)
        }
    }

    func viewController(for url: URLConvertible, context: Any? = nil) -> UIViewController? {
        let urlPatterns = Array(self.viewControllerFactories.keys)
        guard let match = self.matcher.match(url, from: urlPatterns) else { return nil }
        guard let factory = self.viewControllerFactories[match.pattern] else { return nil }
        return factory(url, match.values, context)
    }

    func handler(for url: URLConvertible, context: Any? = nil) -> URLOpenHandler? {
        let urlPatterns = Array(self.handlerFactories.keys)
        guard let match = self.matcher.match(url, from: urlPatterns) else { return nil }
        guard let handler = self.handlerFactories[match.pattern] else { return nil }
        return { handler(url, match.values, context) }
    }
}

extension Router {
    /// 设置视图控制器的参数
    private func setParameters(for viewController: UIViewController?, with parameters: [String: Any]?) {
        guard let viewController = viewController, let parameters = parameters else { return }
        // 1. 使用 KVC 进行参数设置， KVC 需要@objc oc里面才能使用
        // 2. KVC 失败采用反射进行赋值
        for (key, value) in parameters {
            // 创建 setter 方法的名称，例如 setUserName: -> userName
            let setterMethodName = "set\(key.capitalized):"
            let selector = NSSelectorFromString(setterMethodName)
            // 使用 responds(to:) 方法检查 viewController 是否有这个 setter 方法
            if viewController.responds(to: selector) {
                viewController.setValue(value, forKey: key)
            } else {
               _ = RouteError.invalidParamsKey(from: viewController, key: key, value: value)
            }
        }
    }
    
    /// 通用导航方法，减少重复代码
    private func performNavigation(to route: Route, animated: Bool, wrap: UINavigationController.Type? = nil, from: UIViewController? = nil) -> UIViewController? {
        let interceptorChain = InterceptorChain(interceptors: interceptors)
        let destinationViewController: UIViewController? = route.viewController
        setParameters(for: destinationViewController, with: route.parameters)
        
        interceptorChain.proceed(route: .appRoute(route)) { [weak self] shouldNavigate in
            guard let self = self else { return }
            if !shouldNavigate {
                self.handleNavigationBlocked(.appRoute(route))
                return
            }
            
            self.handleNavigation(route: route, destinationViewController: destinationViewController, animated: animated, wrap: wrap, from: from)
        }
        return destinationViewController
    }
    
    private func handleNavigation(route: Route, destinationViewController: UIViewController?, animated: Bool, wrap: UINavigationController.Type?, from: UIViewController?) {
        switch route.transitionStyle {
        case .push(let from, let animated):
            guard let navigationController = self.resolveNavigationController(from: from) else {
                self.handleNavigationError(RouteError.invalidNavigationController(for: route), route: route)
                return
            }
            navigationController.pushViewController(destinationViewController!, animated: animated)
            
        case .present(let from, let wrap, let animated):
            self.presentViewController(route: route, from: from, wrap: wrap, destination: destinationViewController!, animated: animated)
            
        case .detail(let from, let wrap):
            self.detailViewController(route: route, from: from, wrap: wrap, destination: destinationViewController!)
            
        case .custom(let customTransition):
            customTransition(navigationController ?? UIViewController.topMost?.navigationController, UIViewController.topMost)
            
//        case .customModal(let from, let wrap, let type):
//            self.presentCustomModal(route: route, from: from, wrap: wrap, destination: destinationViewController!, type: type)
//            
//        case .navigation(let from, let animated, let type):
//            self.pushWithCustomAnimation(route: route, destinationViewController: destinationViewController!, from: from, animated: animated, type: type)
            
        case .customModal(let from, let wrap):
            self.presentCustomModal(route: route, from: from, wrap: wrap, destination: destinationViewController!)
            
        case .navigation(let from, let animated):
            self.pushWithCustomAnimation(route: route, destinationViewController: destinationViewController!, from: from, animated: animated)
            
        case .root(let window):
            UIView.transition(with: window, duration: 0.5, options: .transitionFlipFromLeft, animations: {
                window.rootViewController = destinationViewController!
            }) { _ in self.interceptors.forEach { $0.didNavigate(to: .appRoute(route)) } }
        }
    }
    
    private func resolveNavigationController(from: UIViewController?) -> UINavigationController? {
        return from?.navigationController ?? navigationController ?? UIViewController.topMost?.navigationController
    }
    
    private func presentViewController(route: Route, from: UIViewController?, wrap: UINavigationController.Type?, destination: UIViewController, animated: Bool) {
        guard let fromViewController = from ?? UIViewController.topMost else {
            self.handleNavigationError(RouteError.cannotPresent(from: route), route: route)
            return
        }
        let viewControllerToPresent = wrap != nil ? wrap!.init(rootViewController: destination) : destination
        DispatchQueue.main.async {
            fromViewController.present(viewControllerToPresent, animated: animated) {
                self.interceptors.forEach { $0.didNavigate(to: .appRoute(route)) }
            }
        }
    }
    
    private func presentCustomModal(route: Route, from: UIViewController?, wrap: UINavigationController.Type?, destination: UIViewController) {
        guard let fromViewController = from ?? UIViewController.topMost else {
            self.handleNavigationError(RouteError.cannotCustomModal(from: route), route: route)
            return
        }
        let viewControllerToPresent = wrap != nil ? wrap!.init(rootViewController: destination) : destination
        DispatchQueue.main.async {
//            viewControllerToPresent.hero.modalAnimationType = .autoReverse(presenting: type)
            fromViewController.present(viewControllerToPresent, animated: true) {
                self.interceptors.forEach { $0.didNavigate(to: .appRoute(route)) }
            }
        }
    }
    
    private func pushWithCustomAnimation(route: Route, destinationViewController: UIViewController, from: UIViewController?, animated: Bool) {
        guard let navigationController = self.resolveNavigationController(from: from) else {
            self.handleNavigationError(RouteError.invalidNavigationController(for: route), route: route)
            return
        }
//        navigationController.hero.modalAnimationType = .autoReverse(presenting: type)
        navigationController.pushViewController(destinationViewController, animated: animated)
        interceptors.forEach { $0.didNavigate(to: .appRoute(route)) }
    }
    
    private func detailViewController(route: Route, from: UIViewController?, wrap: UINavigationController.Type?, destination: UIViewController) {
        guard let fromViewController = from ?? UIViewController.topMost else {
            self.handleNavigationError(RouteError.cannotDetail(from: route), route: route)
            return
        }
        let viewControllerToDetail = wrap != nil ? wrap!.init(rootViewController: destination) : destination
        DispatchQueue.main.async {
            fromViewController.showDetailViewController(viewControllerToDetail, sender: nil)
        }
        interceptors.forEach { $0.didNavigate(to: .appRoute(route)) }
    }
}

extension Router {
    private func handleNavigationBlocked(_ route: UnionType) {
        print("Navigation to route \(route) was blocked by an interceptor.")
        interceptors.forEach { $0.didFailToNavigate(to: route, error: RouteError.navigationBlocked(by: $0)) }
    }
    
    private func handleNavigationError(_ error: RouteError, route: Route) {
        interceptors.forEach { $0.didFailToNavigate(to: .appRoute(route), error: error) }
    }
}
